Skip to content

feat(subagent): rescan a session as soon as its CLI reports idle - #155

Merged
devsuitup merged 1 commit into
mainfrom
feat/cli-session-state-rescan
Aug 23, 2026
Merged

feat(subagent): rescan a session as soon as its CLI reports idle#155
devsuitup merged 1 commit into
mainfrom
feat/cli-session-state-rescan

Conversation

@devsuitup

Copy link
Copy Markdown
Owner

detectSubagentTransitions advances its stability clock only when it runs. #153 gave it a 5 s settle tick so a silent folder can no longer stall it; the lateness that remains is up to one tick plus the rest of the stability window.

The Claude CLI already publishes a per-session state file at ~/.claude/sessions/<pid>.json whose status flips to idle when a turn ends. Watching that directory is an earlier and more precise trigger for the same scan, and costs nothing while nothing changes state — one fs.watch over a handful of tiny files, no polling, no timer armed at rest (ADR 0002).

The idle edge is a trigger, never a verdict. Nothing in the new module marks a subagent complete or emits subagent-completedgrep for either in cli-session-state.js returns nothing. The stability clock stays the sole judge.

Guards

  • The pid is probed for liveness: the file is written on change, not as a heartbeat, so a killed CLI leaves its last status engraved.
  • procStart catches pid reuse — a changed value reseeds the entry as a new process, so the accompanying status change is not read as a transition.
  • A first sighting is never a transition; the directory is seeded once at attach, bounded to 200 files.
  • Rescans are throttled to one per second per session.

Matching is by sessionId against realSessionId || key, so forked and resumed sessions keep working — realSessionId is load-bearing twice, since after a fork the CLI writes the new id while activeSessions is still keyed by the old one, and it is also the id the subagents/ directory is named after. cwd is deliberately not used as a fallback: several sessions can share a working directory.

Every failure — missing directory, truncated JSON, unknown status, no matching session — degrades to doing nothing, with #153's tick as the safety net.

Canary

~/.claude/sessions/<pid>.json is not a documented interface. test/canary-cli-session-state.test.js pins its shape and skips itself where the CLI is absent, so it is never red on CI or on a machine without the CLI. A CLI-side change then reads as a CLI-side change instead of starting a bug hunt here:

PINNED ASSUMPTION BROKEN: "status" was one of {occupied, idle, waiting, shell}, got "busy"
— cli-session-state.js rescans on "idle" only, so a renamed or added status makes the
early rescan silently stop firing (18176.json, CLI version 2.1.241)

This is the repo's first canary-*.test.js; the convention is written up in .ai/contexts/cli-session-state.md.

Reservation, recorded rather than glossed over

The waiting branch was never observed empirically — no permission dialog occurred during the 10-minute, 295-sample run used to characterise the file. It is treated as not-idle on the strength of its name alone; being wrong there only costs a missed early rescan, never a wrong verdict.

Tests

12 new (11 behavioural + the canary), each proven by reverting the corresponding guard:

drop the status check      → waiting must not rescan
drop the liveness probe    → a state file whose process is gone must be inert
drop the procStart guard   → a different procStart is a different process
drop field validation      → 2 tests red
realSessionId || key → key → forked session times out
drop the onIdle call       → 6 tests red

The skip path was verified by pointing HOME at an empty directory: pass 0, fail 0, skipped 1.

725 tests, 718 pass, 0 fail, 7 pre-existing skips. ESLint: 0 errors, 265 warnings, unchanged — the three new files contribute none.

The stability clock in detectSubagentTransitions only advances when the
scan runs. PR #153 gave it a 5 s settle tick so a silent folder can no
longer stall it; the remaining lateness is up to one tick plus the rest
of the stability window.

The Claude CLI already publishes a per-session state file at
~/.claude/sessions/<pid>.json whose status flips to idle when a turn
ends. Watching that directory gives an earlier, more precise trigger for
the same scan, at no cost while nothing changes state.

The idle edge is a trigger, never a verdict: nothing here marks a
subagent complete or emits subagent-completed, and every guard failure
degrades to doing nothing so the settle tick stays the safety net. The
file is not a documented interface, so a canary test pins its shape and
skips itself where the CLI is absent.
@devsuitup
devsuitup force-pushed the feat/cli-session-state-rescan branch from dca9342 to 8a4174d Compare August 23, 2026 23:47
@devsuitup
devsuitup merged commit ab22b1a into main Aug 23, 2026
7 checks passed
@devsuitup
devsuitup deleted the feat/cli-session-state-rescan branch August 23, 2026 23:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant